package org.jeecg.modules.device.service.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.DeviceConstant;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.SysThirdAppConfigVo;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.dynamic.db.DynamicDBUtil;
import org.jeecg.common.util.encryption.AesEncryptUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.device.entity.DevDeviceInfo;
import org.jeecg.modules.device.entity.DevDeviceRecord;
import org.jeecg.modules.device.entity.DevReservoirInfo;
import org.jeecg.modules.device.mapper.DevDeviceInfoMapper;
import org.jeecg.modules.device.mapper.DevDeviceRecordMapper;
import org.jeecg.modules.device.mapper.DevReservoirInfoMapper;
import org.jeecg.modules.device.service.IDevDeviceInfoService;
import org.jeecg.modules.drive.entity.DevDriveModel;
import org.jeecg.modules.drive.mapper.DevDriveModelMapper;
import org.jeecg.modules.drive.service.DriveCommonDbService;
import org.jeecg.modules.drive.service.IDriveCtwingService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.util.*;

/**
 * @Description: 设备信息表
 * @Author: jeecg-boot
 * @Date:   2024-04-17
 * @Version: V1.0
 */
@Service
public class DevDeviceInfoServiceImpl extends ServiceImpl<DevDeviceInfoMapper, DevDeviceInfo> implements IDevDeviceInfoService {
    @Autowired
    private DevDeviceInfoMapper devDeviceInfoMapper;
    @Autowired
    private DriveCommonDbService driveCommonService;
    @Autowired
    private DevDeviceRecordMapper deviceRecordMapper;
    @Autowired
    private DevDriveModelMapper driveModelMapper;
    @Autowired
    private DevReservoirInfoMapper devReservoirInfoMapper;
    @Autowired
    private IDriveCtwingService driveCtwingService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private ISysBaseAPI sysBaseAPI;


    /**
     * 根据设备序列号查询
     * @param deviceCode
     * @return
     */
    @Override
    public DevDeviceInfo queryByCod(String deviceCode) {
        return devDeviceInfoMapper.queryByCode(deviceCode);
        // return devDeviceInfoMapper.selectOne(Wrappers.<DevDeviceInfo>lambdaQuery()
        //         .eq(DevDeviceInfo::getDeviceCode, deviceCode).last("limit 1"));

    }

    /**
     * 根据平台设备ID
     * @param deviceId
     * @return
     */
    @Override
    public DevDeviceInfo queryByDeviceId(String deviceId) {
        return devDeviceInfoMapper.queryByDeviceId(deviceId);
    }

    /**
     * 保存设备信息
     * @param deviceInfo
     */
    @Override
    public Integer saveDeviceInfo(DevDeviceInfo deviceInfo){
        if(StringUtils.isEmpty(deviceInfo.getDeviceId())){
            // 编号作为设备序列号 - 设备身份ID 需要手动
            throw new JeecgBootException("设备序列号不能为空");
        }
        return devDeviceInfoMapper.insert(deviceInfo);
    }

    /**
     * 根据监测点（水库ID）获取水库下所有设备
     * @param reservoirId
     */
    @Override
    public List<DevDeviceInfo> getDeviceListByReservoirId(Long reservoirId){
        return devDeviceInfoMapper.selectList(Wrappers.<DevDeviceInfo>lambdaQuery()
                .eq(DevDeviceInfo::getReservoirId, reservoirId)
                .eq(DevDeviceInfo::getDelFlag, 0));
    }

    /***
     * 同步所有监测点设备远程数据
     */
    @Override
    public void synRemoteData(){
        //缓存获取租户ID列表
        Set<Object> tenantSet = redisUtil.sGet(TenantConstant.SYS_TENANT_CACHE_SET);
        if(null != tenantSet && tenantSet.size()>0){
            for (Object tenantL : tenantSet) {
                TenantContext.setTenant(tenantL.toString());
                List<DevReservoirInfo> infoList = devReservoirInfoMapper.selectList(null);
                for (DevReservoirInfo info : infoList) {
                    this.readMonitorPointDeviceRemoteData(info.getId());
                }
            }
        }

        if(null == tenantSet || tenantSet.size()  == 0){
            List<DevReservoirInfo> infoList = devReservoirInfoMapper.selectList(null);
            for (DevReservoirInfo info : infoList) {
                this.readMonitorPointDeviceRemoteData(info.getId());
            }
        }
    }


    /**
     *  读取监测点设备远程数据
     *
     *  1.读取设备远程数据
     *  2.保存设备数据
     *  3.缓存设备数据-同一设备仅保留一条（覆盖）
     *
     * @param reservoirId 监测点（水库ID）
     */
    @Override
    public void readMonitorPointDeviceRemoteData(String reservoirId){
        /**1.读取设备远程数据*/

        // 当前租户
        String tenantId = oConvertUtils.getString(TenantContext.getTenant(), "0");

        List<Map<String, Object>> deviceDataLs = driveCommonService.readReservoirData(reservoirId);
        if(null != deviceDataLs && deviceDataLs.size()>0){
            for (Map<String, Object> map : deviceDataLs) {
                String uniqueId = (String) map.get("uniqueId");

                DevDeviceRecord deviceRecord = new DevDeviceRecord();
                deviceRecord.setDeviceType(map.get("deviceType").toString());
                deviceRecord.setDeviceId(uniqueId);
                deviceRecord.setDeviceValue(map.get("deviceValue").toString());
                deviceRecord.setDeviceUnit(map.get("deviceUnit").toString());
                /**2.保存设备数据入库*/
                deviceRecordMapper.insert(deviceRecord);

                /**3.缓存设备数据*/
                JSONObject mapJson = new JSONObject();
                mapJson.put("deviceType", map.get("deviceType").toString());
                mapJson.put("deviceValue", map.get("deviceValue").toString());
                mapJson.put("deviceUnit", map.get("deviceUnit").toString());
                mapJson.put("uniqueId", uniqueId);
                mapJson.put("onlineAt", DateUtils.getDate("yyyy-MM-dd hh:mm:ss"));
                redisUtil.hset(DeviceConstant.DEVICE_REALTIME_DATA+"::"+tenantId,uniqueId,mapJson);
            }
        }
    }

    /**
     * 下发指令
     * @param devDeviceInfo
     */
    @Override
    public JSONObject deviceCommand(@NotNull DevDeviceInfo devDeviceInfo) {
        DevDeviceInfo deviceInfo = devDeviceInfoMapper.selectById(devDeviceInfo.getId());
        // 设备驱动模型
        DevDriveModel driveModel = driveModelMapper.selectById(deviceInfo.getDriveId());
        if(null == driveModel){
            throw new JeecgBootException("设备没有配置数据驱动模型");
        }
        try {
            // 第三方应用ID
            String apiResultUniqueId = driveModel.getApiResultUniqueId();
            SysThirdAppConfigVo appConfigVo = sysBaseAPI.getThirdAppConfig(apiResultUniqueId);

            if("open".equals(devDeviceInfo.getCommandType())){
                JSONObject paramJson = new JSONObject();
                paramJson.put("deviceId",deviceInfo.getDeviceId());
                paramJson.put("operator",appConfigVo.getOperator());
                paramJson.put("productId",deviceInfo.getProductId());
                paramJson.put("ttl",36000); // 10小时

                JSONObject contentJson = new JSONObject();
                String itemValue = ""; //
                // 天翼设备下发指令
                List<DictModel> dictItems = sysBaseAPI.getDictItems("AIot_Device_cmd");
                for (DictModel dictItem : dictItems) {
                    String text = dictItem.getText();
                    itemValue = dictItem.getValue();
                    if("openCmd".equals(text)){
                        break;
                    }
                }
                contentJson.put("dataType",2);
                contentJson.put("payload",itemValue);

                JSONObject rsObj = driveCtwingService.sdkNbCreateCommand(appConfigVo.getClientId(), appConfigVo.getClientSecret(),
                        appConfigVo.getAgentAppSecret(), paramJson, contentJson);
                return rsObj;
            }else if("close".equals(devDeviceInfo.getCommandType())){
                JSONObject paramJson = new JSONObject();
                paramJson.put("deviceId",deviceInfo.getDeviceId());
                paramJson.put("operator",appConfigVo.getOperator());
                paramJson.put("productId",deviceInfo.getProductId());
                paramJson.put("ttl",36000); // 10小时

                JSONObject contentJson = new JSONObject();
                String itemValue = ""; //
                // 天翼设备下发指令
                List<DictModel> dictItems = sysBaseAPI.getDictItems("AIot_Device_cmd");
                for (DictModel dictItem : dictItems) {
                    String text = dictItem.getText();
                    itemValue = dictItem.getValue();
                    if("closeCmd".equals(text)){
                        break;
                    }
                }
                contentJson.put("dataType",2);
                contentJson.put("payload",itemValue);

                JSONObject rsObj = driveCtwingService.sdkNbCreateCommand(appConfigVo.getClientId(), appConfigVo.getClientSecret(),
                        appConfigVo.getAgentAppSecret(), paramJson, contentJson);
                return rsObj;
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 保存设备实时缓存数据
     * @param deviceId 设备序列号 - 第三方设备ID
     * @param deviceType
     * @param deviceUnit
     * @param apPdataHex 终端报文数据16进制
     *
     * 681008939784574057660844000627004221350e818b01230e03000f3436303131333134373334323639382405291532002022082500010c170001240529153200020000000000000000000000000000000916
     *  [68]是起始字符       2   2
     *  [10]是设备类型 0 表示该设备大类是远传水表，1 表示设备小类是有阀控脉冲计量，即该设备是有阀控脉冲计量远传水表  2   4
     *  [08]是设备参数，0 表示保留，8 表示设备地址长度为 8  2   6
     *  [9397845740576608] 由地址长度可知 [93 97 84 57 40 57 66 08]是设备地址，16 22
     *  [4400] 表示数据长度为 4*16+4=68 个字节    4   26
     *  [06]表示命令字 1 是心跳 2   28
     *  [27 00]表示心跳数据的长度为 2*16+7= 39个字节 4   32
     *  [42]表示信号强度为(cd -51dBm)  2   34
     *  [21] //信号值2(保留) 2   36
     *  [980e] //电池电压（mv）（小端模式）3736mv   4   40
     *  [818b0123] //版本号码   8   48
     *  [0e] //状态字 阀门(0-1)、干簧管(2-4)、错误(5)、掉电状态(6)   2   50
     *  [0300] //其他（保留） 4   54
     *  [0f] //imsi长度   2 56
     *  [343630313133313437333432363938] //ascii码 30 86
     *  [240529153200] //（BCD码）上传本地时间   12  98
     *  [202208250001] //生产序列号，蓝牙地址     12  110
     *  [0c] //控制码2 抄表数据上报               2   112
     *  [1700] //控制命令长度（小端模式）长度23     4   116
     *  [01] //上传抄表数据记录数，（1-2个）        2   118
     *
     *  [240529153200] //（BCD码）第一次抄表时间  12   130
     *  [02000000] //累计圈数 2圈                8   138
     *  [00000000] //保留（小端模式）支持反转的是反转圈数 8 146
     *  [00000000] //保留（小端模式）             8   154
     *  [00000000] //保留（小端模式）             8   162
     *
     *  [09] 校验和                             2   164
     *  [16] 结束字符                           2    166
     */
    @Override
    public void saveDeviceReadData(String deviceId, String deviceType, String deviceUnit, String apPdataHex) {
        if(StringUtils.isNotEmpty(apPdataHex) && apPdataHex.length() > 160) {
            // 当前租户
            String tenantId = oConvertUtils.getString(TenantContext.getTenant(), "0");

            String readStr = apPdataHex.substring(116); //上传抄表数据
            String timesStr = readStr.substring(0, 2); //上传抄表数据记录数
            int times = Integer.parseInt(timesStr);
            if (times > 1) {
                // 存在多次
                // String readTimesStr = readStr.substring(2, 46);// 第一次抄表读数报文
                // String readDate = readTimesStr.substring(0, 12); // 抄表日期
                // String readDataHex = readTimesStr.substring(12, 20); // 抄表读数
                // int readData = Integer.parseInt(readDataHex, 16); // 16进制读数转10进制
            } else {
                String readTimesStr = readStr.substring(2, 46);// 第一次抄表读数报文
                System.out.println("第一次抄表读数报文:"+ readTimesStr);
                String readDate = readTimesStr.substring(0, 12); // 抄表日期 240315102400
                String readDataHex = readTimesStr.substring(12, 20); // 抄表读数

                String readDataHexCov = AesEncryptUtil.reversalStrByPos(readDataHex, 2);

                int readData = Integer.parseInt(readDataHexCov, 16); // 16进制读数转10进制
                int readNumber = readData / 100;

                DevDeviceRecord deviceRecord = new DevDeviceRecord();
                deviceRecord.setDeviceType(deviceType);
                deviceRecord.setDeviceId(deviceId);
                deviceRecord.setDeviceValue(readData+"");
                deviceRecord.setDeviceUnit(deviceUnit);
                deviceRecord.setUpdateTime(DateUtils.getDate());
                /**2.保存设备数据入库*/
                deviceRecordMapper.insert(deviceRecord);

                /**3.保存动态数据源*/
                List<DictModel> dictItems = sysBaseAPI.getDictItems("remote_meter_dbkey");//远程水表动态数据源Key
                if(null != dictItems && dictItems.size() > 0){

                    // remote_213,jeecg-boot,yccb_sbcb,sbnm,sjyl,addtime
                    String[] values = null;
                    for (DictModel dictItem : dictItems) {
                        if("Db213".equals(dictItem.getText())){
                            values = dictItem.getValue().split(",");
                            break;
                        }
                    }
                    String sql = "INSERT INTO " + values[2]+"(id,"+ values[3] +","+ values[4] +","+ values[5] +") VALUES (?,?,?,?)";
                    String idStr = IdUtil.getSnowflakeNextIdStr();
                    List paramLs = new ArrayList();
                    paramLs.add(idStr);
                    paramLs.add(deviceId);
                    paramLs.add(readNumber);
                    paramLs.add(DateUtils.formatDate("yyyy-MM-dd hh:mm:ss"));
                     DynamicDBUtil.update(values[0], sql, paramLs);
                }

                /**4.缓存设备数据*/
                JSONObject jsonMap = new JSONObject();
                jsonMap.put("deviceType", deviceType);
                jsonMap.put("deviceValue", readNumber);
                jsonMap.put("deviceUnit", deviceUnit);
                jsonMap.put("uniqueId", deviceId);
                try {
                    String date = DateUtils.formatDate(DateUtils.parseDate(readDate, "yyMMddhhmmss"), "yyyy-MM-dd hh:mm:ss");
                    jsonMap.put("updateTime", date);
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
                redisUtil.hset(DeviceConstant.DEVICE_REALTIME_DATA+"::"+tenantId, deviceId, jsonMap);
            }
        }
    }
}
